import numpy.random
import scipy.special


class neuralNetwork:

    def __init__(self, inputnodes, hiddennodes, outputnodes, learningrate):
        """输入层 隐藏层 输出层 学习率"""
        self.inodes = inputnodes
        self.hnodes = hiddennodes
        self.onodes = outputnodes
        self.lr = learningrate

        # 参数矩阵 输入-隐藏层 隐藏-输出层
        # 常规方法
        # rand 范围是 [0,1) 减小0.5 -> [-0.5,0.5)
        # self.wih = (numpy.random.rand(self.hnodes, self.inodes) - 0.5)
        # self.who = (numpy.random.rand(self.onodes, self.hnodes) - 0.5)
        # 复杂方法，计算链接数量的随机权重（正态分布 1/sqrt(传入连接数目)
        # 中心点 0.0 标准方差 pow(self.hnodes, -0.5) = self.hnodes^(-1/2)
        self.wih = numpy.random.normal(0.0, pow(self.hnodes, -0.5), (self.hnodes, self.inodes))
        self.who = numpy.random.normal(0.0, pow(self.onodes, -0.5), (self.onodes, self.hnodes))
        # 定义激活函数
        # expit 就是sigmoid函数
        self.activation_function = lambda x: scipy.special.expit(x)

    def train(self, inputs_list, target_list):
        """训练 1、前向传播输入计算（和query类似） 2、反向误差传播计算"""
        inputs = numpy.array(inputs_list, ndmin=2).T
        targets = numpy.array(target_list, ndmin=2).T

        # 计算输出
        hidden_inputs = numpy.dot(self.wih, inputs)
        hidden_outputs = self.activation_function(hidden_inputs)
        final_inputs = numpy.dot(self.who, hidden_outputs)
        final_outputs = self.activation_function(final_inputs)

        # 计算误差
        output_errors = targets - final_outputs
        # 隐藏层的误差
        hidden_errors = numpy.dot(self.who.T, output_errors)

        # 更新权重
        # Wj,k =  learning_rate * Ek * sigmoid(Ok) * (1-sigmoid(Ok) . T(Oj)
        # 隐藏层-输出层的权重更新
        self.who += self.lr * numpy.dot((output_errors * final_outputs * (1.0 - final_outputs)),
                                        numpy.transpose(hidden_outputs))
        # 输入层-隐藏层的权重更新
        self.wih += self.lr * numpy.dot((hidden_errors * hidden_outputs * (1.0 - hidden_outputs)),
                                        numpy.transpose(inputs))

    def query(self, inputs_list):
        """
        接收输入，经过隐藏层、输出层后，返回输出
        :return:
        """
        # 转成2d数组
        inputs = numpy.array(inputs_list, ndmin=2).T
        # 计算去隐藏层的输入
        hidden_inputs = numpy.dot(self.wih, inputs)
        # 计算隐藏层的输出
        hidden_outputs = self.activation_function(hidden_inputs)
        # 计算去输出层的输入
        final_inputs = numpy.dot(self.who, hidden_outputs)
        # 计算输出层的输出
        final_outputs = self.activation_function(final_inputs)
        return final_outputs


# print(n.wih, n.who)
# print(n.query([1.0, 0.5, -1.5]))


#
input_nodes = 784  # 28*28
hidden_nodes = 100
output_nodes = 10  # 10个数字的概率
learningrate = 0.2
n = neuralNetwork(input_nodes, hidden_nodes, output_nodes, learningrate)

training_data_file = open("mnist_train.csv", 'r')
training_data_list = training_data_file.readlines()
training_data_file.close()
# 训练
for record in training_data_list:
    all_values = record.strip().split(",")
    scaled_input = numpy.asarray(all_values[1:]).astype(numpy.float32)
    # 读取训练数据并处理，把数据范围归一化到(0,0.99] 避免0值
    inputs = scaled_input / 255.0 * 0.99 + 0.01
    # 目标输出的格式化，转成概率数组 (0,1)
    targets = numpy.zeros(output_nodes) + 0.01
    targets[int(all_values[0])] = 0.99
    n.train(inputs, targets)

# 测试
test_data_file = open("mnist_test.csv", 'r')
test_data_list = test_data_file.readlines()
test_data_file.close()

scorecard = []
for record in test_data_list:
    all_values = record.strip().split(",")
    scaled_input = numpy.asarray(all_values[1:]).astype(numpy.float32)
    inputs = scaled_input / 255.0 * 0.99 + 0.01
    output = n.query(inputs)
    # 找出数值最大的索引
    label = numpy.argmax(output)
    correct = int(all_values[0])
    if label == correct:
        scorecard.append(1)
    else:
        scorecard.append(0)
scorecard_array = numpy.asarray(scorecard)
print("score=", scorecard_array.sum() / scorecard_array.size)
